package investigation;

import precision.*;

/**
 * This seeks to explore the average time taken to do a certain 
 * number of addition/multiplication/division operations for an FPObj
 * 
 * Prints out the mean and Standard Deviation of the Mean for each
 * 
 * @author Ben Jones
 *
 */

public class Performance {


	private static FPObj[] X;
	private static FPObj[][] Y;
	private static int numIter;
	private static int numRuns;
	private static int numB;
	private static int numTypes;
	private static long precision;

	/**
	 * Multiplies A = A * B[j] for all j, and then repeats for numIter time steps
	 * @param A FPObj
	 * @param B Array of FPObj
	 * @return double of time taken for method to run
	 */
	public static double multiply(FPObj A, FPObj B[]){		
		long now = System.currentTimeMillis();		
		for(int i = 0; i<numIter; i++){
			for(int j = 0; j<B.length; j++){
				A = A.multi(B[j]);
			}
		}
		long time = System.currentTimeMillis()-now;
		//System.out.println("Time in ms for Multiplication of type "+A.toType()+" = "+time);
		return (double)time; 
	}

	/**
	 * Adds A = A + B[j] for all j, and then repeats for numIter time steps
	 * @param A FPObj
	 * @param B Array of FPObj
	 * @return double of time taken for method to run
	 */
	public static double addition(FPObj A, FPObj B[]){		
		long now = System.currentTimeMillis();		
		for(int i = 0; i<numIter; i++){
			for(int j = 0; j<B.length; j++){
				A = A.add(B[j]);
			}
		}
		long time = System.currentTimeMillis()-now;
		//System.out.println("Time in ms for Addition of type "+A.toType()+" = "+time);
		return (double)time; 
	}

	/**
	 * Divides A = A / B[j] for all j, and then repeats for numIter time steps
	 * @param A FPObj
	 * @param B Array of FPObj
	 * @return double of time taken for method to run
	 */
	public static double division(FPObj A, FPObj B[]){
		long now = System.currentTimeMillis();		
		for(int i = 0; i<numIter; i++){
			for(int j = 0; j<B.length; j++){
				A = A.divide(B[j]);
			}
		}
		long time = System.currentTimeMillis()-now;
		//System.out.println("Time in ms for Division of type "+A.toType()+" = "+time);
		return (double)time; 
	}


	/**
	 * Runs the arithmetic methods numRuns times whilst keeping track of the mean time taken
	 * and the standard deviation in the mean time.
	 * 
	 * @param A FPObj
	 * @param B Array of FPObj's
	 */
	public static void run(FPObj A, FPObj B[]){
		double timeM = 0, timeA = 0, timeD = 0; // Records time taken for each arithmetic operation after numIter operations
		double avgM = 0, avgA = 0, avgD = 0; // Sums time for numRuns steps before dividing by numRuns for average
		double avgM2 = 0, avgA2 = 0, avgD2 = 0; // Sums time*time for numRuns steps before dividing by numRuns for average of squared
		System.out.println("Beginning run for "+A.toType()+"\n");
		int maxPercentage = 0;
		for(int i = 0; i<numRuns; i++){
			timeM = multiply(A,B);
			timeA = addition(A,B);
			timeD = division(A,B);
			avgM += timeM;
			avgA += timeA;
			avgD += timeD;
			avgM2 += (timeM*timeM);
			avgA2 += (timeA*timeA);
			avgD2 += (timeD*timeD);
			if((((i+1)*100)/numRuns%10) == 0 && (((i+1)*100)/numRuns)>maxPercentage){
				System.out.println("Percentage Complete: "+((i+1)*100)/numRuns+"%");// Tracks percentage complete
				maxPercentage = ((i+1)*100)/numRuns;
			}
		}	
		// Divide by numRuns for correct averages
		avgM = avgM/((double)numRuns);
		avgA = avgA/((double)numRuns);
		avgD = avgD/((double)numRuns);
		avgM2 = avgM2/((double)numRuns);
		avgA2 = avgA2/((double)numRuns);
		avgD2 = avgD2/((double)numRuns);		
		double sigmaM = 0, sigmaA = 0, sigmaD = 0; // Used to find the standard dev in mean
		sigmaM = (Math.sqrt(avgM2-(avgM*avgM)))/(Math.sqrt((double)numRuns)); // Standard formula for SDoM
		sigmaA = (Math.sqrt(avgA2-(avgA*avgA)))/(Math.sqrt((double)numRuns));
		sigmaD = (Math.sqrt(avgD2-(avgD*avgD)))/(Math.sqrt((double)numRuns));
		System.out.println("\nFor Class type: "+A.toType()+" and "+numIter+" iterations per arithmetic operation "
				+"\nMuliplication: Average Time: "+avgM+"ms with sigma: "+sigmaM+"ms"
				+"\nAddition: Average Time: "+avgA+"ms with sigma: "+sigmaA+"ms"
				+"\nDivision: Average Time: "+avgD+"ms with sigma: "+sigmaD+"ms\n\n");
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// Sets values for numRuns of all arithmetic operations, numIters for each
		// arithmetic operation, the length of the B FPObj array, and the numTypes of
		// FPObj classes (i.e. float, double, APFloat..)
		numRuns = 50;
		numIter = 100000;	
		numB = 100;
		numTypes = 12;
		precision = 2;
		// Creates a 1D and 2D array. X contains different FPObj representations of 5.
		// Y contains different FBObj representations of array of increasing 10, 100, 1000...
		X = new FPObj[numTypes];
		Y = new FPObj[numTypes][numB];		
		X[0] = (FPObj) (new Floats((float)5));		
		for(int i = 0; i<Y[0].length; i++){
			Y[0][i] = (FPObj)(new Floats((float)(i+1)*10));
		}		
		X[1] = (FPObj) (new Doubles((double)5));		
		for(int i = 0; i<Y[1].length; i++){
			Y[1][i] = (FPObj)(new Doubles((double)(i+1)*10));
		}
		for (int j = 2; j<numTypes; j++){
			X[j] = (FPObj) (new Arbitrary((int)5,(int)Math.pow(precision, (j-1))));		
			for(int i = 0; i<Y[j].length; i++){
				Y[j][i] = (FPObj)(new Arbitrary((int)(i+1)*10,(int)Math.pow(precision, (j-1))));
			}	
		}
		// Runs through the arithmetic operations for each FBObj type.
		for(int i = 0; i<numTypes; i++){
			run(X[i],Y[i]);
		}
	}
}
